package com.mvcframework.servlet;

import com.mvcframework.annotations.Autowired;
import com.mvcframework.annotations.Controller;
import com.mvcframework.annotations.RequestMapping;
import com.mvcframework.annotations.Security;
import com.mvcframework.annotations.Service;
import com.mvcframework.handler.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Created with IntelliJ IDEA by ChouFy on 2020-03-04.
 *
 * @author zhoufy
 */
public class DispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();

    /**
     * 初始化
     *
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1、加载配置文件
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        // 2、扫描相关注解的类
        doScan(properties.getProperty("scanPackage"));

        // 3、初始化bean对象
        doInstance();

        // 4、实现依赖注入
        doAutowired();

        // 5、构造一个handlerMapping处理器映射器 将配置好的url 和 method 建立映射关系
        initHandlerMapping();

        System.out.println("finish init");
    }

    /**
     * 接受处理请求
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    /**
     * 处理请求
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        resp.setCharacterEncoding("utf-8");
        resp.setContentType("text/html;charset=utf-8");

        Handler handler = handlerMapping.get(req.getRequestURI());

        if(handler != null){
            Object controller = handler.getController();
            Method method = handler.getMethod();

            Class<?>[] parameterTypes = method.getParameterTypes();

            Object[] parameters = new Object[parameterTypes.length];

            Map<String, String[]> parameterMap = req.getParameterMap();

            String[] userNames = handler.getValues();

            boolean flag = true;
            if(userNames == null || userNames.length == 0){
                flag = false;
            }

            for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
                String value = StringUtils.join(param.getValue(), ",");

                if(handler.getMap().containsKey(param.getKey())){
                    int index = handler.getMap().get(param.getKey());
                    parameters[index] = value;
                }
                if(flag){
                    if(Arrays.asList(userNames).contains(value)){
                        flag = false;
                    }
                }
            }
            if(flag){
                resp.getWriter().println("没有权限");
                return ;
            }

            Integer reqIndex = handler.getMap().get(HttpServletRequest.class.getSimpleName());
            if(reqIndex != null){
                parameters[reqIndex] = req;
            }

            Integer rspIndex = handler.getMap().get(HttpServletResponse.class.getSimpleName());
            if(rspIndex != null){
                parameters[rspIndex] = resp;
            }

            try {
                method.invoke(controller, parameters);
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 加载配置文件
     *
     * @param contextConfigLocation
     */
    private void doLoadConfig(String contextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 缓存扫描到的类的全限定名
     */
    private List<String> classNameList = new ArrayList<>();

    /**
     * 扫描相关的类
     *
     * @param scanPackage
     */
    private void doScan(String scanPackage) {
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        System.out.println(scanPackagePath);
        File pack = new File(scanPackagePath);
        File[] files = pack.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                // 如果是文件夹
                doScan(scanPackage + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNameList.add(className);
            }
        }
    }

    private Map<String, Object> ioc = new HashMap<>();

    /**
     * 基于classNameList缓存的类的全限定名 使用反射 完成初始化
     */
    private void doInstance() {
        if (classNameList.size() == 0) {
            return;
        }
        for (String className : classNameList) {
            try {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(Controller.class)) {
                    String simpleName = lowerFirst(clazz.getSimpleName());
                    Object o = clazz.newInstance();
                    ioc.put(simpleName, o);
                } else if (clazz.isAnnotationPresent(Service.class)) {
                    Service annotation = clazz.getAnnotation(Service.class);
                    String beanName = annotation.value();
                    //  指定beanID
                    if ("".equals(beanName)) {
                        beanName = lowerFirst(clazz.getSimpleName());
                    }
                    ioc.put(beanName, clazz.newInstance());

                    //  获取接口  以接口名称作为beanid 放入ioc中
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (int i = 0; i < interfaces.length; i++) {
                        Class<?> interfaceClass = interfaces[i];
                        ioc.put(interfaceClass.getSimpleName(), clazz.newInstance());
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private String lowerFirst(String name) {
        return name.substring(0, 1).toLowerCase() + name.substring(1);
    }

    /**
     * 实现依赖注入
     */
    private void doAutowired() {
        if (ioc.isEmpty()) {
            return;
        }
        // 进行依赖注入处理

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            for (int i = 0; i < declaredFields.length; i++) {
                Field declaredField = declaredFields[i];
                if (!declaredField.isAnnotationPresent(Autowired.class)) {
                    continue;
                }
                Autowired annotation = declaredField.getAnnotation(Autowired.class);
                String beanName = annotation.value();
                if ("".equals(beanName)) {
                    //  没有指定类id 根据当前字段类型注入
                    beanName = declaredField.getType().getSimpleName();
                }
                declaredField.setAccessible(true);
                try {
                    declaredField.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private Map<String, Handler> handlerMapping = new HashMap<>();


    /**
     * 构造一个handlerMapping 处理映射器
     * 将url和method建立关联
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();
            if (!aClass.isAnnotationPresent(Controller.class)) {
                continue;
            }
            String baseUrl = "";
            if (aClass.isAnnotationPresent(RequestMapping.class)) {
                RequestMapping annotation = aClass.getAnnotation(RequestMapping.class);
                baseUrl = annotation.value();
            }

            Method[] methods = aClass.getMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                if (!method.isAnnotationPresent(RequestMapping.class)) {
                    continue;
                }
                RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                String methodUrl = annotation.value();

                baseUrl = baseUrl + methodUrl;

                Handler handler = new Handler(entry.getValue(), method, baseUrl);

                if (method.isAnnotationPresent(Security.class)) {
                    Security security = method.getAnnotation(Security.class);
                    handler.setValues(security.value());
                }


                Parameter[] parameters = method.getParameters();
                for (int j = 0; j < parameters.length; j++) {
                    Parameter param = parameters[j];
                    if (param.getType() == HttpServletRequest.class || param.getType() == HttpServletResponse.class) {
                        handler.getMap().put(param.getType().getSimpleName(), j);
                    } else {
                        handler.getMap().put(param.getName(), j);
                    }
                }
                handlerMapping.put(baseUrl, handler);
            }
        }
    }
}
